package org.kamionowski.supreme.services.crawler.impl;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.vfs.*;
import org.apache.commons.vfs.provider.ftp.FtpFileSystemConfigBuilder;
import org.kamionowski.supreme.persistence.entity.Service;
import org.kamionowski.supreme.services.crawler.Crawler;
import org.kamionowski.supreme.services.crawler.exc.CrawlerException;
import org.kamionowski.supreme.services.global.ServicesService;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;

import javax.inject.Inject;

/**
 * Created by IntelliJ IDEA.
 * User: Marcin Kamionowski
 * Date: 06.04.11
 * Time: 23:46
 */
@org.springframework.stereotype.Service
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class CrawlerImpl implements Crawler
{
    private final static Log log = LogFactory.getLog(CrawlerImpl.class);
    @Inject
    protected ServicesService servicesService;
    @Inject
    protected CrawlerContext context;


    @Override
    public void scanService(Integer serviceId) throws CrawlerException {
        Validate.notNull(serviceId);
        Service service = servicesService.selectById(serviceId);
        scanService(service);
    }

    protected void scanService(Service service) throws CrawlerException{
        try{
            log.debug("Creating VFS manager for " + service);
            context.setService(service);
            FileSystemOptions opts = new FileSystemOptions();
            FtpFileSystemConfigBuilder config = FtpFileSystemConfigBuilder.getInstance();
            config.setUserDirIsRoot(opts, true);
            FileSystemManager fsManager = VFS.getManager();
            FileObject root = fsManager.resolveFile( service.generateURL().toString(), opts);
            if(root.exists()){
                notifyBeginScanning();
                scanPath(context, root);
                notifyEndScanning();
            }
            //todo: throw exc
        }catch(FileSystemException exc){
            throw new CrawlerException("Cannot scan service " + service, exc, service);
        }
    }

    protected void scanPath(CrawlerContext context, FileObject node) throws CrawlerException{
        try{
            log.info("Scan node " + node + ".");
            if(!node.exists()){
                log.info("Node " + node + " not exists.");
                return;
            }
            FileType nodeType = node.getType();
            if(FileType.FOLDER.equals(nodeType)){
                scanDir(context, node);
            }else if(FileType.FILE.equals(nodeType)){
                scanFile(node);
            }else{
                scanUnsupportedFileType(node);
            }
        }catch (FileSystemException exc){
            log.warn("Cannot scan node " + node + ": " + exc.getMessage());
            notifyAccessDenied(node);
        }
    }

    protected void scanDir(CrawlerContext context, FileObject node)throws FileSystemException, CrawlerException{
        try{
            log.info("Scan dir content " + node + ".");
            FileObject[] children = node.getChildren();
            log.debug("Dir " + node + " contains " + (children == null ? 0 : children.length) + " nodes.");
            if(!ArrayUtils.isEmpty(children)){
                for(FileObject child : children){
                    scanPath(context, child);
                }
            }
        }catch (FileSystemException exc){
            throw new CrawlerException(exc, context.getService());
        }
    }

    protected void scanFile(FileObject node) throws FileSystemException, CrawlerException{
        log.info("Scan file content " + node);
        notifyFileDetected(node);
    }

    private void scanUnsupportedFileType(FileObject node)throws FileSystemException{
        log.info("Scan unknown (" + node.getType() + ") file type: " + node.toString());
    }

    private void notifyFileDetected(FileObject node)throws CrawlerException{
        for(CrawlerStateListener listener: context.getListeners()){
            listener.fileDetected(RemoteFile.build(node, context.getService()));
        }
    }

    private void notifyAccessDenied(FileObject node) throws CrawlerException{
         for(CrawlerStateListener listener:context.getListeners()){
             listener.accessDenied(RemoteFile.build(node, context.getService()));
         }
    }

    private void notifyBeginScanning(){
         for(CrawlerStateListener listener:context.getListeners()){
             listener.begin(context.getService());
         }
    }

    private void notifyEndScanning(){
         for(CrawlerStateListener listener:context.getListeners()){
             listener.end(context.getService());
         }
    }

    @Required
    public void setServicesService(ServicesService servicesService) {
        this.servicesService = servicesService;
    }

    @Required
    public void setContext(CrawlerContext context) {
        this.context = context;
    }
}
